package com.study;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.sql.SQLOutput;
import java.util.Arrays;

/**
 * AOP:aspect oriented programming 面向切面编程，其实就是面向特定方法编程
 * 场景：案例中部分接口运行比较慢，定位执行耗时较长的接口，此时需要统计每一个接口方法的执行耗时
 * 优势：减少重复代码、代码无侵入、提高开发效率、维护方便
 * 注意：AOP是一种思想，而在Spring框架中对这种思想进行的实现，那我们要学习的就是Spring AOP
 * <p>
 * 开发步骤：引入AOP依赖、编写AOP的程序（公共的逻辑代码）
 * <p>
 * 应用场景：记录系统的操作日志、权限控制、事务管理
 * <p>
 * 执行流程：SpringAOP旨在管理bean对象的过程中，主要通过底层的动态代理机制，对特定的方法进行编程
 * <p>
 * 核心概念
 * 连接点JoinPoint，可以被AOP控制的方法(暗含方法执行时的相关信息)（能够被AOP控制的方法，即潜在对象）
 * 通知Advice，指那些重复的逻辑，也就是共性功能，最终体现为一个方法
 * 切入点pointcut，匹配连接的条件，通知仅会在切入点方法执行时被应用（真正被AOP控制的方法，即目标）
 * 切面aspect，描述通知与切入点的对应关系（通知+切入点表达式）
 * 目标对象target，通知所应用的对象（切入点所在的类
 */

/**
 * 统计耗时
 */

@Aspect
@Component
public class DemoAspect {
    //Around环绕通知：joinpoint。proceed（）即执行，记得要return   Before前置通知：目标方法前执行  After后置通知：目标方法后执行，无论是否异常或者返回都执行
    //AfterRetuning 后置返回通知，只有正常返回时才执行   AfterThrowing 只有异常才执行


/*
    切入点表达式：
        方法一："execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)"
        其中带? 的表示可以省略的部分
            1.访问修饰符：可省略（比如：public、protected）
            2.包名.类名：可省略
            3.throws 异常：可省略（注意是方法上声明抛出的异常，不是实际抛出的异常）
        * :单个独立的任意符号，可以通配任意返回值、包名、类名、方法名、任意类型的一个参数，也可以统配包、类、方法名的一部分
            execution(* com.*.service.*.update*(*))
        ..:多个连续的任意符号，可以通配任意层级的包、或任意类型、任意个数的参数（一般只用在参数上）
        也可以将多个execution用||连接 例如：execution(.............)||execution(..........)
            execution（* com.itheima..DeptService.*(..)）
        书写建议
        a.所有业务方法名在命名时尽量规范，方便切入点表达式快速匹配。如:findXxx,updateXxx
        b.描述切入点方法通常基于接口描述，而不是直接描述实现类，增强拓展性
        c.在满足业务需要的前提下，尽量缩小切入点的匹配范围，如：包名尽量不用..，使用*匹配单个包

        方法二:@annotation（"自定义注解的全类限定名"）  例如@Before("@annotation(com.study.anno.Log)")
                第一步：自定义注解的元注解改为RUNTIME时使用、并且在方法上使用。 将自定义注解Log写在切入点上面
                第二部：在通知上写@annotation（"自定义注解的全类限定名"）
        execution切入点表达式和@annotation切入点表达式的应用场景
            如果execution切入点表达式方便描述指定的方法，就用execution表达式
            否则，就使用@annotation切入点表达式
*/

    //抽取公共切入点表达式
    @Pointcut("execution(* com.study.HelloController.*(..))")
    public void pc() {
    }

    /*若有多个切面的切入点都匹配到了目标方法，目标方法运行时，多个通知方法都会执行
    * 执行顺序：不同切面类中，默认按照切面类的类名排序（目标方法前的字母排名靠前的先，目标方法后相反）
    *        可以在切面类上加@Order注解（数字）控制顺序  （目标方法前数字越靠前越先，目标方法后相反）【后面微服务中讲网关会用到】
    * */

    //    @Around("execution(* com.study.HelloController.*(..))")
    @Around("pc()")
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        String className = joinPoint.getTarget().getClass().getName();
        System.out.println("目标类名："+className);
        Signature signature = joinPoint.getSignature();
        System.out.println("目标方法签名："+signature);
        String methodName = joinPoint.getSignature().getName();
        System.out.println("目标方法名："+methodName);
        Object[] args = joinPoint.getArgs();
        System.out.println("目标方法运行参数："+Arrays.toString(args));

        long begin = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        System.out.println("返回值："+result);
        long end = System.currentTimeMillis();
        System.out.println("耗时" + (end - begin) + "ms");
        return result;
    }

    //    @Before("execution(* com.study.HelloController.*(..))")
    @Before("pc()")
    public void before(JoinPoint joinPoint) {
        //Around获取连接点只能用ProceedJoinPoint，而其他四种获取连接点只能用JointPoint
        System.out.println("-----------------------------------------------------");
        String className = joinPoint.getTarget().getClass().getName();
        System.out.println("目标类名："+className);
        Signature signature = joinPoint.getSignature();
        System.out.println("目标方法签名："+signature);
        String methodName = joinPoint.getSignature().getName();
        System.out.println("目标方法名："+methodName);
        Object[] args = joinPoint.getArgs();
        System.out.println("目标方法运行参数："+Arrays.toString(args));
        System.out.println("前置");
        System.out.println("-----------------------------------------------------");

    }

    //    @After("execution(* com.study.HelloController.*(..))")
    @After("pc()")
    public void after() {
        System.out.println("后置");
    }

    //    @AfterReturning("execution(* com.study.HelloController.*(..))")
    @AfterReturning("pc()")
    public void ar() {
        System.out.println("后置返回");
    }

    //    @AfterThrowing("execution(* com.study.HelloController.*(..))")
    @AfterThrowing("pc()")
    public void at() {
        System.out.println("后置异常");
    }
}
